package coreSimulation;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Random;
import java.util.Scanner;

import utils.Logger;
import exceptions.DuplicatePassengerException;

/**
 * The Queue class represents the initial queue of passengers wishing to drop
 * their baggage off prior to boarding a flight. Passengers in the Queue class
 * are processed by the DropOffDesk class
 * 
 * @author Andrew Stokes
 * 
 */
public class Queue extends Observable implements Iterable<Passenger>, Runnable {

	private String inputFile;
	private LinkedList<Passenger> queue = new LinkedList<Passenger>();

	/**
	 * Constructs an empty Queue
	 */
	public Queue(String inputFile) {
		this.inputFile = inputFile;

	}

	/**
	 * @param passenger
	 *            - passenger object to be added to the queue
	 * @throws IllegalArgumentException
	 *             thrown if passenger parameter is null
	 * 
	 * @throws DuplicatePassengerException
	 *             if passenger with the same booking ref. already exists in
	 *             queue
	 * 
	 *             Adds passenger object to the queue
	 * 
	 * @return true if passenger successfully added to the queue
	 */
	public synchronized boolean add(Passenger passenger) throws DuplicatePassengerException {
		boolean result = false;

		if (passenger == null) {
			throw new IllegalArgumentException();
		} else if (queue.contains(passenger)) {
			throw new DuplicatePassengerException(passenger);
		}

		else {
			queue.add(passenger);
			// mark as value changed
			setChanged();
			// trigger notification
			notifyObservers();
			result = true;
		}
		return result;
	}

	 
	
	public boolean isEmpty() {
		return queue.isEmpty();
	}

	/**
	 * @param passenger
	 * @return true if the queue contains the passenger passed to this method
	 */
	public boolean contains(Passenger passenger) {
		return (queue.contains(passenger));
	}

	/**
	 * Method accepts a comma delimited list of name and booking ref values
	 * These values are parsed then used to instantiate Passenger and baggage
	 * objects. The generated passenger objects are then added to the Queue. As
	 * there is a 1-1 relationship between passengers and baggage the baggage
	 * information for each passenger is also stored in this file
	 * 
	 * @param fileName
	 *            Either full or relative path to a CSV file containing details
	 *            of passengers for bulk import. Format for lines in CSV input
	 *            file <br/>
	 * <br/>
	 *            Name, Booking Ref, Bag width (cm), Bag height (cm), Bag depth
	 *            (cm), Bag weight (kg) <br/>
	 * <br/>
	 * @return int value is returned indicating the number of passenger objects
	 *         successfully instantiated and imported into the Queue
	 * 
	 */
	public int buildQueue() {

		int numberOfErrors = 0;
		int numberOfSuccessfulImports = 0;
		int lineNo = 0;
		Logger logger = Logger.getLogger();

		try {
			Random random = new Random();
			// open the CSV file containing Passenger and Baggage information
			File file;
			file = new File(inputFile);

			// create a Scanner to iterate through teach line of the CSV file
			Scanner fileScanner;
			fileScanner = new Scanner(file);

			// iterate through the file
			while (fileScanner.hasNextLine()) {
				lineNo++;

				// read each line in turn
				String inputLine = fileScanner.nextLine();

				// separate name and booking ref elements
				String inputElements[] = inputLine.split(",");

				try {
					// instantiate passenger object
					Passenger p = new Passenger(inputElements[0],
							inputElements[1]);

					// instantiate baggage object for passenger
					Baggage b = new Baggage(Integer.parseInt(inputElements[2]),
							Integer.parseInt(inputElements[3]),
							Integer.parseInt(inputElements[4]),
							Double.parseDouble(inputElements[5]));

					// associate baggage with passenger
					p.addBaggage(b);

					// add passenger to the queue
					add(p);
					numberOfSuccessfulImports++;

				} catch (IllegalStateException ise) {

					// if the values for the passenger object read from the CSV
					// file aren't valid we'll end up here after an
					// IllegalStateExceptionis thrown by the constructor
					// of the Passenger class
					logger.appendLog("Error parsing line " + lineNo
							+ " containing \"" + inputLine + "\". Error: "
							+ ise.getMessage());
					numberOfErrors++;

				} catch (DuplicatePassengerException dpe) {
					// if there is already a passenger in the list with the same
					// booking ref a DuplicatePassengerException
					// will be thrown
					logger.appendLog("Error parsing line " + lineNo
							+ " containing \"" + inputLine + "\". Error: "
							+ dpe.getMessage());
					numberOfErrors++;

				} catch (Exception e) {
					logger.appendLog("Error parsing line " + lineNo
							+ " containing \"" + inputLine + "\". Error: "
							+ e.getMessage());
					numberOfErrors++;
				}
				Thread.sleep(random.nextInt(1000));

			}
			// tidy the file scanner up
			fileScanner.close();

			// report any import errors to the standard output
			if (numberOfErrors > 0) {
				logger.appendLog("Number of file parsing errors = "
						+ numberOfErrors);
			}
			return numberOfSuccessfulImports;
		} catch (FileNotFoundException fnfe) {

			// if the file specified in the parameters to this class doesn't
			// exist report problem to standard output
			logger.appendLog("Failed to parse file " + inputFile
					+ ". File not found - error: " + fnfe.getMessage());
			return numberOfSuccessfulImports;
		} catch (Exception e) {
			// if an unexpected exception occurs report this to standard output
			logger.appendLog("Failed to parse file " + inputFile + ". Error: "
					+ e.getMessage());
			return numberOfSuccessfulImports;
		} finally {

		}

	}

	/**
	 * 
	 * @param index
	 *            - location of the passenger in the queue
	 * @return - passenger object removed from the queue
	 */
	public synchronized Passenger remove() {
		try {
			Passenger p = queue.remove();
			// mark as value changed
			setChanged();
			// trigger notification
			notifyObservers();
			return p;

		} catch (Exception e) {
			return null;
		}
	}
	

	/**
	 * 
	 * @return an iterator of Passenger objects
	 */
	@Override
	public Iterator<Passenger> iterator() {
		return queue.iterator();
	}

	/**
	 * 
	 * @return number of passengers in the queue
	 */
	public int size() {
		return queue.size();
	}

	/**
	 * removes all passengers from the queue
	 */
	public void clear() {
		queue.clear();
		// mark as value changed
		setChanged();
		// trigger notification
		notifyObservers();
	}

	/**
	 * 
	 * @param passenger
	 *            - passenger to be found in queue
	 * @return returns integer specifying the location of the passenger in the
	 *         queue. Returns -1 if passenger not in the queue
	 */
	public int indexOf(Passenger passenger) {
		return queue.indexOf(passenger);
	}

	@Override
	public void run() {

		this.buildQueue();

	}

}